package jp.syuriken.snsw.terenaibot.mal.twitter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.MissingResourceException;
import java.util.logging.Level;
import java.util.regex.Matcher;

import jp.syuriken.snsw.terenaibot.BotConfiguration;
import jp.syuriken.snsw.terenaibot.ConfigurationException;
import jp.syuriken.snsw.terenaibot.ConfigurationExceptionType;
import jp.syuriken.snsw.terenaibot.Const;
import jp.syuriken.snsw.terenaibot.DataProvider;
import jp.syuriken.snsw.terenaibot.NullPost2;
import jp.syuriken.snsw.terenaibot.PluginContainer;
import jp.syuriken.snsw.terenaibot.Post2;
import jp.syuriken.snsw.terenaibot.PostWrapper2;
import jp.syuriken.snsw.terenaibot.UserInfo;
import jp.syuriken.snsw.terenaibot.WrapperException2;
import jp.syuriken.snsw.terenaibot.format.DataProviderFormatPrefix;
import jp.syuriken.snsw.terenaibot.format.FormatPrefixHolder;
import jp.syuriken.snsw.terenaibot.format.FormatSyntaxException;
import jp.syuriken.snsw.terenaibot.format.PostFormatter;
import jp.syuriken.snsw.terenaibot.format.PrecompiledSyntax;
import jp.syuriken.snsw.utils.Logger;
import jp.syuriken.snsw.utils.MyResourceBundle;
import twitter4j.DirectMessage;
import twitter4j.IDs;
import twitter4j.Paging;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.StatusUpdate;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.TwitterResponse;
import twitter4j.User;
import twitter4j.conf.ConfigurationBuilder;

/**
 * ツイッター用のポストラッパ。直接的な使用は避けるべきです。
 * 
 * @author $Author: snswinhaiku.lo@gmail.com $
 */
public class TwitterPostWrapper extends PostWrapper2 {
	
	/**
	 * 容量を固定したリスト。
	 * 
	 * @author $Author: snswinhaiku.lo@gmail.com $
	 */
	protected class FixedCapacityList {
		
		/** 容量 */
		protected int capacity;
		
		long[] array;
		

		/**
		 * インスタンスを生成する。
		 * 
		 * @param capacity 容量
		 */
		public FixedCapacityList(int capacity) {
			array = new long[capacity];
			this.capacity = capacity;
		}
		
		/**
		 * リストに追加する。
		 * 
		 * @param statusId 値
		 */
		public void add(long statusId) {
			array[0] = statusId;
			sort();
		}
		
		/**
		 * リストに追加する。配列のサイズがcapacityより大きい場合、前からcapacity分しか追加されません。
		 * 
		 * @param values 値の配列。
		 */
		public void addAll(int[] values) {
			for (int i = 0; i < values.length && i < capacity; i++) {
				array[i] = values[i];
			}
			sort();
		}
		
		/**
		 * valueが含まれているかどうかを調べる。
		 * 
		 * @param statusId 検索する値
		 * @return 含まれているかどうか
		 */
		protected boolean contains(long statusId) {
			return Arrays.binarySearch(array, statusId) >= 0;
		}
		
		/**
		 * ソートする。
		 */
		protected void sort() {
			Arrays.sort(array);
		}
	}
	

	@SuppressWarnings("unchecked")
	private static <T>T tryClone(Object instance) {
		return (T) instance;
	}
	

	private FixedCapacityList gotMentionList;
	
	/** Service Temporarily Unavailableのステータスコード */
	protected static final int STATUS_CODE_TEMPORARIRY_UNAVAILABLE = 503;
	
	/** Twitterインスタンス */
	protected Twitter twitter;
	
	private long lastPrivateMessageId;
	
	/** ユーザー情報格納クラス */
	protected UserInfo userInfo = null;
	
	private Logger logger;
	
	private PluginContainer pluginContainer;
	
	private long lastMentionId;
	
	private long lastTimelineId;
	
	private ArrayList<TwitterUserInfo> followersList;
	
//	/**
//	 * インスタンスを生成する。
//	 * @param conf 設定
//	 * @param twitter Twitterインスタンス
//	 */
//	public TwitterPostWrapper(BotConfiguration conf, Twitter twitter) {
//		super();
//		gotMentionList =
//				new FixedCapacityList(Integer.parseInt(botConfiguration.getResourceBundle().getString(
//						Const.CONF_PAGE_MENTION_MAX)));
//		this.twitter = twitter;
//	}
	
	private ArrayList<TwitterUserInfo> followingList;
	
	private boolean isExceededRateLimitation;
	
	private boolean isExceededPostLimitation;
	
	private PostFormatter postFormatter;
	

	/**
	 * TODO snsoftware
	 * @param e 
	 */
	private TwitterWrapperException checkException(TwitterException e) {
		if (isExceededRateLimitation == false && e.exceededRateLimitation()) {
			setExceededRateLimitation(true);
		}
		if (isExceededRateLimitation && e.exceededRateLimitation() == false) {
			setExceededRateLimitation(false);
		}
		if (isExceededPostLimitation == false) {
			String errorMessage = e.getErrorMessage();
			if ("User is over daily status update limit.".equals(errorMessage)) {
				setExceededPostLimitation(true);
			}
		}
		return new TwitterWrapperException(e);
	}
	
	/**
	 * TODO snsoftware
	 * 
	 */
	private void checkFollowed() {
		if (followersList == null || followingList == null) {
			return;
		}
		ArrayList<TwitterDelayedUserInfo> newFollowerList = tryClone(followersList.clone());
		newFollowerList.removeAll(followingList);
		TwitterDelayedUserInfo[] newFollowers =
				newFollowerList.toArray(new TwitterDelayedUserInfo[newFollowerList.size()]);
		
		newFollowerList = TwitterDelayedUserInfo.getUserInfos(twitter, newFollowers);
		pluginContainer.hookNewFollower(newFollowerList.toArray(new TwitterDelayedUserInfo[newFollowerList.size()]));
	}
	
	@Override
	public boolean exceedApiLimitation() {
		return isExceededRateLimitation;
	}
	
	@Override
	public boolean exceedPostLimitation() {
		return isExceededPostLimitation;
	}
	
	@Override
	public boolean follow(String userId) throws TwitterWrapperException {
		if (pluginContainer.hookBeforeFollow(userId)) {
			try {
				long userIdLong = Long.parseLong(userId);
				if (followingList.contains(new TwitterDelayedUserInfo(userIdLong, twitter))) {
					return false;
				}
				User user = twitter.createFriendship(userIdLong);
				followingList.add(new TwitterUserInfo(user));
				pluginContainer.hookAfterFollow(new TwitterUserInfo(user));
				return true;
			} catch (NumberFormatException e) {
				throw e;
			} catch (TwitterException e) {
				if (e.getStatusCode() == TwitterException.FORBIDDEN) {
					return false;
				} else {
					throw checkException(e);
				}
			}
		} else {
			return false;
		}
	}
	
	@Override
	public UserInfo[] getFollowers(boolean doCleanCache) throws TwitterWrapperException {
		if (doCleanCache || followersList == null) {
			long cursor = -1L;
			ArrayList<TwitterUserInfo> followersTempList = new ArrayList<TwitterUserInfo>();
			IDs followerIds = null;
			do {
				try {
					followerIds = twitter.getFollowersIDs(cursor);
				} catch (TwitterException e) {
					logger.log(Level.WARNING, e, "TwitterPostWrapper#getFollowers");
					checkException(e);
				}
				
				for (long id : followerIds.getIDs()) {
					followersTempList.add(new TwitterDelayedUserInfo(id, twitter));
				}
				cursor = followerIds.getNextCursor();
			} while (followerIds.hasNext());
			followersList = followersTempList;
			checkFollowed();
		}
		
		return followersList.toArray(new UserInfo[followersList.size()]);
	}
	
	@Override
	public UserInfo[] getFollowing(boolean doCleanCache) throws TwitterWrapperException {
		if (doCleanCache || followingList == null) {
			long cursor = -1L;
			ArrayList<TwitterUserInfo> followingTempList = new ArrayList<TwitterUserInfo>();
			IDs followingIds = null;
			do {
				try {
					followingIds = twitter.getFriendsIDs(cursor);
				} catch (TwitterException e) {
					logger.log(Level.WARNING, e, "TwitterPostWrapper#getFolloings");
					checkException(e);
				}
				for (long id : followingIds.getIDs()) {
					followingTempList.add(new TwitterDelayedUserInfo(id, twitter));
				}
				cursor = followingIds.getNextCursor();
			} while (followingIds.hasNext());
			followingList = followingTempList;
			checkFollowed();
		}
		
		return followingList.toArray(new UserInfo[followingList.size()]);
	}
	
	@Override
	public Post2[] getMentions() throws TwitterWrapperException {
		Paging paging =
				new Paging(1, Integer.parseInt(botConfiguration.getResourceBundle().getString(
						Const.CONF_PAGE_MENTION_MAX, "20")), lastMentionId);
		try {
			ResponseList<Status> mentions = twitter.getMentions(paging);
			ArrayList<Post2> posts = new ArrayList<Post2>();
			for (Status status : mentions) {
				long statusId = status.getId();
				if (lastMentionId < statusId) {
					lastMentionId = statusId;
				}
				if (gotMentionList.contains(statusId)) {
					continue;
				}
				posts.add(new TwitterPost(status, getUserName()));
			}
			
			botConfiguration.getDataProviderFactory().getProvider("instance")
				.setString("postw.twitter.lastid.mention", String.valueOf(lastMentionId));
			Post2[] postArray = posts.toArray(new Post2[posts.size()]);
			botConfiguration.getPluginContainer().hookGotTimeline(postArray);
			return postArray;
		} catch (TwitterException e) {
			botConfiguration.getLogger().log(Level.INFO, e, "TwitterPostWrapper#getMentions");
			checkException(e);
		} catch (IllegalStateException e) {
			botConfiguration.getLogger().log(Level.WARNING, e, "TwitterPostWrapeer#getMentions");
		} catch (TwitterWrapperException e) {
			botConfiguration.getLogger().log(Level.INFO, e, "TwitterPostWrapeer#getMentions");
		}
		return new Post2[0];
	}
	
	protected UserInfo getMyUserInfo() throws TwitterException {
		if (userInfo == null) {
			userInfo = new TwitterUserInfo(twitter.verifyCredentials());
		}
		return userInfo;
	}
	
	@Override
	public Post2 getPost(String postId) throws IllegalStateException, TwitterWrapperException {
		try {
			return new TwitterPost(twitter.showStatus(Long.parseLong(postId)), getUserName());
		} catch (NumberFormatException e) {
			throw e;
		} catch (TwitterException e) {
			checkException(e);
			return NullPost2.getInstance();
		}
	}
	
	@Override
	public Object getPostActionClass() {
		return twitter;
	}
	
	/**
	 * TODO snsoftware
	 * 
	 * @param inReplyTo
	 * @return
	 */
	private FormatPrefixHolder getPrefixHolderUnderPosting(Post2 inReplyTo, Matcher matcher) {
		FormatPrefixHolder prefixHolder = new FormatPrefixHolder();
		prefixHolder.put("user", new TwitterUserPostPrefix(inReplyTo, twitter));
		prefixHolder.put("data", new DataProviderFormatPrefix(inReplyTo.getUser()));
		prefixHolder.put("rgrp", new MatcherFormatPrefix(matcher));
		return prefixHolder;
	}
	
	@Override
	public Post2[] getPrivateMessage() throws TwitterWrapperException {
		Paging paging =
				new Paging(0, Integer.parseInt(botConfiguration.getResourceBundle().getString(
						Const.CONF_PAGE_PRIVATE_MESSAGE_MAX, "20")), lastPrivateMessageId);
		try {
			ResponseList<DirectMessage> directMessages = twitter.getDirectMessages(paging);
			ArrayList<Post2> posts = new ArrayList<Post2>();
			for (DirectMessage directMessage : directMessages) {
				long directMessageId = directMessage.getId();
				if (lastPrivateMessageId < directMessageId) {
					lastPrivateMessageId = directMessageId;
				}
				posts.add(new TwitterPost(directMessage));
			}
			botConfiguration.getDataProviderFactory().getProvider("instance")
				.setString("postw.twitter.lastid.dmessage", String.valueOf(lastPrivateMessageId));
			return posts.toArray(new Post2[posts.size()]);
		} catch (TwitterException e) {
			botConfiguration.getLogger().log(Level.INFO, e, "TwitterPostWrapper#getDirectMessage");
			checkException(e);
			return new Post2[0];
		}
	}
	
	private String getStringFromConfiguration(MyResourceBundle resourceBundle, String configName)
			throws ConfigurationException {
		try {
			return resourceBundle.getString(configName);
		} catch (MissingResourceException e) {
			throw new ConfigurationException(botConfiguration.getConfigurationFile(), configName,
					ConfigurationExceptionType.NOT_FOUND);
		}
	}
	
	@Override
	public Post2[] getTimeline() throws TwitterWrapperException {
		Paging paging =
				new Paging(1, Integer.parseInt(botConfiguration.getResourceBundle().getString(
						Const.CONF_PAGE_TIMELINE_MAX, "20")), lastTimelineId);
		try {
			ResponseList<Status> timeline = twitter.getHomeTimeline(paging);
			ArrayList<Post2> posts = new ArrayList<Post2>();
			for (Status status : timeline) {
				long statusId = status.getId();
				if (lastTimelineId < statusId) {
					lastTimelineId = statusId;
				}
				if (gotMentionList.contains(statusId)) {
					continue;
				}
				TwitterPost post = new TwitterPost(status, getUserName());
				switch (post.getPostType()) {
					case MULTICAST:
					case UNICAST:
						gotMentionList.add(statusId);
						break;
					default:
						break;
				}
				posts.add(post);
			}
			
			botConfiguration.getDataProviderFactory().getProvider("instance")
				.setString("postw.twitter.lastid.timeline", String.valueOf(lastTimelineId));
			Post2[] postArray = posts.toArray(new Post2[posts.size()]);
			botConfiguration.getPluginContainer().hookGotTimeline(postArray);
			return postArray;
		} catch (TwitterException e) {
			botConfiguration.getLogger().log(Level.INFO, e, "TwitterPostWrapper#getTimeline");
			checkException(e);
		} catch (IllegalStateException e) {
			botConfiguration.getLogger().log(Level.WARNING, e, "TwitterPostWrapeer#getTimeline");
		} catch (TwitterWrapperException e) {
			botConfiguration.getLogger().log(Level.INFO, e, "TwitterPostWrapeer#getTimeline");
		}
		return new Post2[0];
	}
	
	@Override
	public UserInfo getUserInfo() throws TwitterWrapperException {
		try {
			return getMyUserInfo();
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public UserInfo getUserInfo(String userId) throws TwitterWrapperException, NumberFormatException {
		long id = Long.parseLong(userId);
		try {
			return new TwitterUserInfo(id, twitter);
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public String getUserName() throws IllegalStateException, TwitterWrapperException {
		if (userInfo == null) {
			return getUserName(true);
		} else {
			return userInfo.getScreenName();
		}
	}
	
	private String getUserName(boolean isFirstAction) throws IllegalStateException, TwitterWrapperException {
		try {
			getMyUserInfo();
			return userInfo.getScreenName();
		} catch (IllegalStateException e) {
			throw e;
		} catch (TwitterException e) {
			int statusCode = e.getStatusCode();
			if (isFirstAction && statusCode == STATUS_CODE_TEMPORARIRY_UNAVAILABLE) {
				logger.log(Level.INFO, "Twitter returned status-code" + STATUS_CODE_TEMPORARIRY_UNAVAILABLE
						+ " while getUserName(); retrying...");
				return getUserName(false);
			} else {
				throw checkException(e);
			}
		}
	}
	
	@Override
	public String getWrapperName() {
		return "TwitterPostWrapper";
	}
	
	@Override
	public TwitterPostWrapper init(BotConfiguration configuration) throws ConfigurationException {
//		this.configuration = configuration;
		logger = configuration.getLogger();
		pluginContainer = configuration.getPluginContainer();
		
		MyResourceBundle resourceBundle;
		botConfiguration = configuration;
		resourceBundle = configuration.getResourceBundle();
		
		ConfigurationBuilder cb = new ConfigurationBuilder();
		cb.setDebugEnabled(true)
			.setOAuthConsumerKey(getStringFromConfiguration(resourceBundle, "twitter.oauth.consumer_key"))
			.setOAuthConsumerSecret(getStringFromConfiguration(resourceBundle, "twitter.oauth.consumer_secret_key"))
			.setOAuthAccessToken(getStringFromConfiguration(resourceBundle, "twitter.oauth.access_token"))
			.setOAuthAccessTokenSecret(getStringFromConfiguration(resourceBundle, "twitter.oauth.access_token_secret"));
		TwitterFactory tf = new TwitterFactory(cb.build());
		twitter = tf.getInstance();
		gotMentionList =
				new FixedCapacityList(Integer.parseInt(getStringFromConfiguration(resourceBundle,
						Const.CONF_PAGE_MENTION_MAX)));
		
		DataProvider dataProvider = botConfiguration.getDataProviderFactory().getProvider("instance");
		lastMentionId = Long.parseLong(dataProvider.getString("postw.twitter.lastid.mention", "1"));
		lastPrivateMessageId = Long.parseLong(dataProvider.getString("postw.twitter.lastid.dmessage", "1"));
		lastTimelineId = Long.parseLong(dataProvider.getString("postw.twitter.lastid.timeline", "1"));
		
		postFormatter = botConfiguration.getPostFormatter();
		return this;
	}
	
	private Post2 post(StatusUpdate statusUpdate, boolean isFirstAction) throws IllegalStateException,
			TwitterWrapperException {
		Status updateStatus;
		try {
			updateStatus = twitter.updateStatus(statusUpdate);
		} catch (TwitterException e) {
			if (isFirstAction && e.getStatusCode() == STATUS_CODE_TEMPORARIRY_UNAVAILABLE) {
				return post(statusUpdate, false);
			} else {
				TwitterWrapperException exception = checkException(e);
				exception.setPostString(statusUpdate.getStatus());
				throw exception;
			}
		}
		TwitterPost post2;
		post2 = new TwitterPost(updateStatus, getUserName());
		isExceededPostLimitation = false;
		return post2;
	}
	
	@Override
	public Post2 post(String poststr) throws IllegalStateException, TwitterWrapperException {
		if (pluginContainer.hookBeforePost(poststr)) {
			return post(new StatusUpdate(poststr), true);
		} else {
			return NullPost2.getInstance();
		}
	}
	
	@Override
	public Post2 postInQuote(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo)
			throws IllegalStateException, WrapperException2, FormatSyntaxException {
		// TODO: when quoting, add inReplyTo status-option
		// TODO: when quoting, QT? RT? option
		if (inReplyTo instanceof TwitterPost) {
			TwitterResponse inReplyToPost = ((TwitterPost) inReplyTo).getOriginal();
			FormatPrefixHolder prefixHolder = getPrefixHolderUnderPosting(inReplyTo, matcher);
			String postString = postFormatter.format(postFormat, prefixHolder);
			
			if (inReplyToPost instanceof Status) {
				StatusUpdate statusUpdate =
						new StatusUpdate(String.format("%s RT @%s %s", postString, inReplyTo.getUserName(),
								inReplyTo.getPostActualText()));
				statusUpdate.setInReplyToStatusId(((Status) inReplyToPost).getId());
				return post(statusUpdate, true);
			} else if (inReplyToPost instanceof DirectMessage) {
				return sendPrivateMessage(postString, inReplyTo.getUser());
			}
		}
		botConfiguration.getLogger().log(
				Level.WARNING,
				"PostWrapper#postInQuote: got argument<inReplyTo> is not instance of TwitterPost: "
						+ inReplyTo.toString() + " {postFormat=" + postFormat.toString() + "}");
		try {
			TwitterPost post = new TwitterPost(twitter.updateStatus(postFormatter.format(postFormat)), getUserName());
			pluginContainer.hookAfterPost(post);
			return post;
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public Post2 postInQuote(String postString, Post2 inReplyTo) throws IllegalStateException, TwitterWrapperException {
		// TODO: when quoting, add inReplyTo status-option
		// TODO: when quoting, QT? RT? option
		if (inReplyTo instanceof TwitterPost) {
			TwitterResponse inReplyToPost = ((TwitterPost) inReplyTo).getOriginal();
			if (inReplyToPost instanceof Status) {
				StatusUpdate statusUpdate =
						new StatusUpdate(String.format("%s RT @%s %s", postString, inReplyTo.getUserName(),
								inReplyTo.getPostActualText()));
				statusUpdate.setInReplyToStatusId(((Status) inReplyToPost).getId());
				return post(statusUpdate, true);
			} else if (inReplyToPost instanceof DirectMessage) {
				return sendPrivateMessage(postString, inReplyTo.getUser());
			}
		}
		botConfiguration.getLogger().log(
				Level.WARNING,
				"PostWrapper#postInQuote: got argument<inReplyTo> is not instance of TwitterPost: "
						+ inReplyTo.toString() + " {postString=" + postString + "}");
		try {
			TwitterPost post = new TwitterPost(twitter.updateStatus(postString), getUserName());
			pluginContainer.hookAfterPost(post);
			return post;
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public Post2 postInReply(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo)
			throws IllegalStateException, WrapperException2, FormatSyntaxException {
		if (inReplyTo instanceof TwitterPost) {
			TwitterResponse inReplyToPost = ((TwitterPost) inReplyTo).getOriginal();
			FormatPrefixHolder prefixHolder = getPrefixHolderUnderPosting(inReplyTo, matcher);
			String postString = postFormatter.format(postFormat, prefixHolder);
			if (inReplyToPost instanceof Status) {
				StatusUpdate statusUpdate =
						new StatusUpdate(String.format("@%s %s", inReplyTo.getUserName(), postString));
				statusUpdate.setInReplyToStatusId(((Status) inReplyToPost).getId());
				return post(statusUpdate, true);
			} else if (inReplyToPost instanceof DirectMessage) {
				try {
					return new TwitterPost(twitter.sendDirectMessage(inReplyTo.getInReplyToUserId(), postString));
				} catch (TwitterException e) {
					throw checkException(e);
				}
			}
		}
		botConfiguration.getLogger().log(
				Level.WARNING,
				"PostWrapper#postInReply: got argument<inReplyTo> is not instance of TwitterPost: "
						+ inReplyTo.toString() + " {postFormat=" + postFormat.toString() + "}");
		
		String postString = postFormatter.format(postFormat);
		try {
			TwitterPost post = new TwitterPost(twitter.updateStatus(postString), getUserName());
			pluginContainer.hookAfterPost(post);
			return post;
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public Post2 postInReply(String postString, Post2 inReplyTo) throws IllegalStateException, TwitterWrapperException {
		if (inReplyTo instanceof TwitterPost) {
			TwitterResponse inReplyToPost = ((TwitterPost) inReplyTo).getOriginal();
			if (inReplyToPost instanceof Status) {
				StatusUpdate statusUpdate =
						new StatusUpdate(String.format("@%s %s", inReplyTo.getUserName(), postString));
				statusUpdate.setInReplyToStatusId(((Status) inReplyToPost).getId());
				return post(statusUpdate, true);
			} else if (inReplyToPost instanceof DirectMessage) {
				try {
					return new TwitterPost(twitter.sendDirectMessage(inReplyTo.getInReplyToUserId(), postString));
				} catch (TwitterException e) {
					throw checkException(e);
				}
			}
		}
		botConfiguration.getLogger().log(
				Level.WARNING,
				"PostWrapper#postInReply: got argument<inReplyTo> is not instance of TwitterPost: "
						+ inReplyTo.toString() + " {postString=" + postString + "}");
		try {
			TwitterPost post = new TwitterPost(twitter.updateStatus(postString), getUserName());
			pluginContainer.hookAfterPost(post);
			return post;
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	@Override
	public Post2 postInUserCall(PrecompiledSyntax postFormat, Matcher matcher, UserInfo userId)
			throws IllegalStateException, WrapperException2 {
		return post(String.format("@%s %s", userInfo.getScreenName(), postFormat));
	}
	
	@Override
	public Post2 postInUserCall(String postString, UserInfo userInfo) throws IllegalStateException,
			TwitterWrapperException {
		return post(String.format("@%s %s", userInfo.getScreenName(), postString));
	}
	
	@Override
	public Post2 sendPrivateMessage(String pmString, String userId) throws TwitterWrapperException {
		try {
			return new TwitterPost(twitter.sendDirectMessage(userId, userId));
		} catch (TwitterException e) {
			throw checkException(e);
		}
	}
	
	/**
	 * TODO snsoftware
	 * 
	 * @param b
	 */
	private void setExceededPostLimitation(boolean b) {
		isExceededPostLimitation = b;
		if (b) {
			pluginContainer.exceedPostLimitation();
		} else {
			pluginContainer.resetExceededPostLimitation();
		}
	}
	
	/**
	 * TODO snsoftware
	 * 
	 * @param b
	 */
	private void setExceededRateLimitation(boolean b) {
		isExceededRateLimitation = b;
		if (b) {
			pluginContainer.exceedApiLimitation();
		} else {
			pluginContainer.resetExceededApiLimitation();
		}
	}
}
